import java.awt.Color;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.io.StreamCorruptedException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.TreeSet;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;

import model.*;
import order.*;


public class RISCServer {
	
	static final int DEFAULT_PORT = 2122;
	static ArrayList<RISC.Player> plyrs;
	static ArrayList<Socket> accepted;
	private static TreeSet<String> usernameList;
	private static HashSet<PrintWriter> broadcast;
	private static HashSet<ObjectOutputStream> obj_broadcast;
	static CyclicBarrier barrier, updateBarrier, timeBarrier;
	static Random r = new Random();
	static RISC game;
	
	public static void main(String[] args) throws IOException {
		System.out.println("RISC server started!\n");
		ServerSocket listener = new ServerSocket(DEFAULT_PORT);
		usernameList = new TreeSet<String>();
		broadcast = new HashSet<PrintWriter>();
		obj_broadcast = new HashSet<ObjectOutputStream>();
		plyrs = new ArrayList<RISC.Player>();
		final Object lock1 = new Object();
		
		try {
			game = new RISC(3);
			game.time = 1;
			barrier = new CyclicBarrier(game.max, new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(10000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					game.setupMap(plyrs);
					game.sendMap();
					System.out.println("Map updates sent");
					game.mapUpdate = true;
					game.rdy4Next = false;
				}
			});
			updateBarrier = new CyclicBarrier(game.max, new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						System.out.println("Passing the update barrier (failure?): " + e.getMessage());
					}
					if(!game.isUpdating) {
						game.isUpdating = true;
						System.out.println("Started updateMap()");
						game.updateMap();
						System.out.println("Started sendMap()");
						game.sendMap();
						game.rdy4Next = false;
					}
					System.out.println("Just finished through the time barrier");
					try {
						Thread.sleep(10000L);
					} catch (InterruptedException e) {
						System.out.println("Interrupt in 10 sec wait in updateBarrier:" + e.getMessage());
					}
				}
			});
			timeBarrier = new CyclicBarrier(game.max, new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						System.out.println("Passing the time barrier (failure?): " + e.getMessage());
					}
				}
			});
			while(true) {
				new RISC.Player(listener.accept()).start();
			}
		} finally {
			listener.close();
		}
	}
	
	static class RISC {
		
		private int max = 0, turn = 0;
		private volatile int status = 0;
		private volatile boolean rdy4Next = false;
		private volatile boolean gameOver = false;
		private volatile boolean isUpdating = false;
		private volatile static boolean mapUpdate = false;
		private static int time;
		static Map map;
		static ArrayList<Order> allOrders;
		static ArrayList<Move> pMoves;
		static ArrayList<Attack> pAttacks;
		static ArrayList<String> allMoves;
		static ArrayList<String> allAttacks;
		static ArrayList<String> allUpgrades;
		private volatile static boolean allConnected = false;
		private volatile static int setupDone = 0;
		public RISC(int max) {
			this.max = max;
			map = new Map();
			allOrders = new ArrayList<Order>();
		}
		
		public static void setupMap(ArrayList<RISC.Player> plyrs) {
			//if(!allConnected) {
				for(RISC.Player p : plyrs) {
					while(true) {
						int tryX, tryY;
						while(true) {
							tryX = (int)(Math.random() * ((10 - 0) + 1));
							tryY = 0 + (int)(Math.random() * ((10 - 0) + 1));//0 = min, max = 10
							if(tryX >= 0 && tryX < map.getRows())
								if(tryY >= 0 && tryY < map.getCols())
									break;
						}
						if(map.getTerritory(tryX, tryY).getOwner().equals("")) {
							map.getTerritory(tryX, tryY).setOwner(p.getUserName());
							map.getTerritory(tryX, tryY).SetAmount(5);
							map.getTerritory(tryX, tryY).setUpdated(true);
							map.getTerritory(tryX, tryY).setColor(p.getColor());
							p.addTerritory(map.getTerritory(tryX, tryY));
							p.setAmount(5);
							break;
						}
					}
				}
			//}
		}
		
		public static void sendMap() {
			for(ObjectOutputStream o : obj_broadcast) {
				try {
					o.writeObject("MU:Map en route");
					o.flush();
					o.reset();
					o.writeObject(map);
					System.out.println("Sent map to object outputstream at: " + o.toString());
				} catch (IOException e) {
					System.out.println("Map send failure: " + e.getMessage() + "\n");
				}
			}
			for(RISC.Player p : plyrs) {
				p.sendPlayerInfo();
				System.out.println("Sent information to " + p.getName());
			}
			game.rdy4Next = true;
		}
		
		private static void processOrders() {
			ArrayList<Order> forRemoval = new ArrayList<Order>();
			//synchronized(allOrders) {
				for(Order o : allOrders) {
					if(o instanceof Move && o.getClass() != Order.class) {
						Move temp = (Move) o;
						System.out.println(temp.toString());
						System.out.println("at Move CheckPath()");
						if(temp.checkPath(map)) {
							System.out.println("finished Move checkPath()");
							if(temp.checkOrder(map))
								System.out.println("Order was successfully implemented");
							else {
								System.out.println("Order was not implemented:");
								System.out.println(temp.toString());
							}
						}
						forRemoval.add(o);
						//allOrders.remove(o);
					}
				}
			//}
			for(Order remove : forRemoval) {
				if(allOrders.contains(remove))
					allOrders.remove(remove);
			}
			forRemoval.clear();
			//synchronized(allOrders) {
				System.out.println("processOrders() iterated through Moves");
				for(Order oa : allOrders) {
					if(oa instanceof Attack && oa.getClass() != Order.class) {
						
						Attack temp = (Attack) oa;
						System.out.println("at Attack CheckPath()");
						if(temp.checkPath(map)) {
							if(temp.checkOrder(map)) {
								System.out.println(temp.getDestination().toString());
								System.out.println("Attackers: " + temp.getDestination().getAmtAttacking());
								temp.getDestination().addAttacker(temp);
								System.out.println(temp.getDestination().toString());
								System.out.println("Attackers: " + temp.getDestination().getAmtAttacking());
								System.out.println("Order was successfully added for implementation");
							}
							else {
								System.out.println("Order was not implemented:");
								System.out.println(temp.toString());
							}
								
						}
						forRemoval.add(oa);
						//allOrders.remove(oa);
					}
				}
				System.out.println("Begin checkAttack()");
				checkAttack();
				System.out.println("End checkAttack()");
			//}

			for(Order remove : forRemoval) {
				if(allOrders.contains(remove))
					allOrders.remove(remove);
			}
			forRemoval.clear();
			//synchronized(allOrders) {
				System.out.println("processOrders() iterated through Attacks");
				if(allOrders.size() != 0) {
					System.out.println("Something wasn't processed. See the list of size " + allOrders.size() + " below:");
					for(Order o : allOrders) {
						if(o instanceof Move) {
							Move temp = (Move) o;
							System.out.println(temp.toString());
						} else {
							if(o instanceof Attack) {
								Attack temp = (Attack) o;
								System.out.println(temp.toString());
							}
							else {
								System.out.println("It wasn't classified as either a move or attack?");
								System.out.println(o.toString());
							}
						}
					}		
				}
				System.out.println("processOrders() iterated through Unknowns");
				allOrders.clear();
			//}
		}
		
		public static void checkAttack() {
			for(int i = 0; i < map.getRows(); i++) {
				for(int j = 0; j < map.getCols(); j++) {
					Territory terr = map.getTerritory(i, j);
					if(terr.getAmtAttacking() == 0) {
						System.out.println(i + "," + j + " was not attacked");
					} else {
						if(terr.getAmtAttacking() == 1) {
							if(terr.getOwner().equals(""))
							{
								Attack att = terr.GetAttacker().get(0);
								att.getSource().CutAmount(att.GetAmount());
								terr.SetAmount(terr.GetAmount() + att.GetAmount());
								terr.setOwner(att.User);
								for(int k = 0; k < plyrs.size(); k++) {
									System.out.println(plyrs.get(k).getName());
									if(terr.getOwner().equals(plyrs.get(k).getUserName())) {
										terr.setColor(plyrs.get(k).getColor());
										System.out.println("Found the right color");
										break;
									}
								}
								terr.clearAttacker();
								System.out.println("Territory " + terr.getx() + "," + terr.gety() + " taken over by " +
										"player " + att.User);
								System.out.println(terr.toString());
							}
						} else {
							if(terr.getAmtAttacking() > 1) {
								System.out.println("Begin multipleAttack() instance");
								Attack result = multipleAttacks(terr.GetAttacker());
								result.getSource().CutAmount(result.GetAmount());
								terr.SetAmount(terr.GetAmount() + result.GetAmount());
								terr.setOwner(result.User);
								for(int k = 0; k < plyrs.size(); k++) {
									if(terr.getOwner().equals(plyrs.get(k).getUserName())) {
										terr.setColor(plyrs.get(k).getColor());
										System.out.println("Found the right color");
										break;
									}
								}
								terr.clearAttacker();
								
								System.out.println(result.User + " fought their way to victory and seized Territory " +
										terr.getx() + "," + terr.gety() + "!");
								System.out.println(terr.toString());
							}
						}
					}
					continue;
				}
			}
		}
		
		public static Attack multipleAttacks(ArrayList<Attack> attackers) {
			while(attackers.size() != 1) {
				Attack a1 = attackers.get(0);
				Attack a2 = attackers.get(1);
				int a1roll, a2roll;
				while(a1.GetAmount() != 0 || a2.GetAmount() != 0) {
					a1roll = new Random().nextInt((20 - 1) + 1) + 1;
					a2roll = new Random().nextInt((20 - 1) + 1) + 1;
					if(a1roll != a2roll) {
						if(a1roll > a2roll)
							a2.SetAmount(a2.GetAmount() - 1);
						else
							a1.SetAmount(a1.GetAmount() - 1);
					}
				}
				if(a1.GetAmount() == 0) {
					attackers.remove(a1);
					continue;
				} else {
					attackers.remove(a2);
					continue;
				}
			}
			System.out.println("End multipleAttack() instance");
			return attackers.get(0);
		}
		
		public static void processAllOrders() {
			
			for(String s : allUpgrades) {
				
			}
			for(String s : allMoves) {
				
			}
			for(String s : allAttacks) {
				
			}
			allUpgrades.clear();
			allMoves.clear();
			allAttacks.clear();
		}
		
		public static void updateMap() {
			System.out.println("Begin processOrders()");
			processOrders();
			mapUpdate = true;
		}
		
		static class Player extends Thread {
			
			private Socket connection;
			private BufferedReader in;
			private PrintWriter out;
			private String name;
			private ObjectOutputStream obj_out;
			private static ObjectInputStream obj_in;
			private Color color;
			private int troops;
			private ArrayList<Territory> myHouse;
			private static ArrayList<String> myOrders;
			private static List<Move> myMoves;
			private static List<Attack> myAttacks;
			private boolean lost = false;
			private boolean turnWorkDone = false;
			private Object readLock;
			
			public Player(Socket socket) {
				connection = socket;
				System.out.println("new Player made");
				readLock = new Object();
			}
			
			public String getUserName() {
				return name;
			}
			
			public Color getColor() {
				return color;
			}
			
			public void setAmount(int n) {
				troops = n;
			}
			
			public boolean finishedTurnProcessing() {
				return turnWorkDone;
			}
			
			public void addTerritory(Territory... annexed) {
				for(Territory t : annexed) {
					myHouse.add(t);
				}
			}
			
			public void removeTerritory(Territory... stolen) {
				for(Territory lost : stolen) {
					myHouse.remove(lost);
				}
			}
			
			private Order parseOrder(String order) {
				String[] parts = order.split("\\*");
				if(parts.length != 5) {
					System.out.println("There was a problem with the following order:");
					System.out.println(order);
				}
				Order received;
				parts[0] = parts[0].replace("Type:", "");
				parts[1] = parts[1].replace("Requester:", "");
				parts[2] = parts[2].replace("Source:", "");
				parts[3] = parts[3].replace("Destination:", "");
				parts[4] = parts[4].replace("Amount:", "");
				
				String[] src = parts[2].split(",");
				String[] dest = parts[3].split(",");
				
				if(parts[0].equals("Move")) {
					received = new Move(parts[1], map.getTerritory(Integer.parseInt(src[0]),Integer.parseInt(src[1])), 
							map.getTerritory(Integer.parseInt(dest[0]),Integer.parseInt(dest[1])), Integer.parseInt(parts[4]));
				} else
					if(parts[0].equals("Attack")) {
						received = new Attack(parts[1], map.getTerritory(Integer.parseInt(src[0]),Integer.parseInt(src[1])), 
								map.getTerritory(Integer.parseInt(dest[0]),Integer.parseInt(dest[1])), Integer.parseInt(parts[4]));
					} else
						return null;
				return received;
			}
			
			private static Thread turnRead = new Thread() {
				public void run() {
					while(true) {
						try {
							//myOrders = (ArrayList<Order>) obj_in.readObject();
							myMoves = (ArrayList<Move>) obj_in.readObject();
							myAttacks = (ArrayList<Attack>) obj_in.readObject();
						} catch (ClassNotFoundException | IOException e) {
							System.out.println("Server receive exception: " + e.getMessage());
							break;
						}
					}
				}
			};
			
			public void sendPlayerInfo() {
				try {
					obj_out.writeObject("PI:" + troops + "," + myHouse.size() + ";");
				} catch (IOException e) {
					System.out.println("send player " + name + " info failed: " + e.getMessage());
					e.printStackTrace();
				}
			}
			
			@SuppressWarnings("unchecked")
			public void run() {
				try {
					System.out.println("Player run() started");
					in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
					System.out.println("in made");
					//out = new PrintWriter(connection.getOutputStream(), true);
					//System.out.println("out made");
					obj_out = new ObjectOutputStream(connection.getOutputStream());
					System.out.println("server obj out made");
					//obj_in = new ObjectInputStream(connection.getInputStream());
					//System.out.println("server obj in made");
					System.out.println("Connection from client accepted\n");
					synchronized(plyrs) {
						if(plyrs.size() > game.max) {
							//change this to start a new RISC server game on another RISC thread
							obj_out.writeObject("MSG:The game already has its set number of players! Please try connecting again later");
							try {
								connection.close();
							} catch(IOException e) {
								System.out.println("Server full, connection close: " + e.getMessage());
							}
						} else {
							plyrs.add(this);
							System.out.println("Players now: " + plyrs.size());
							obj_out.writeObject("AC:Accepted your client!");
							//broadcast.add(out);
							obj_broadcast.add(obj_out);
							myHouse = new ArrayList<Territory>();
						}
					}							
					while(true) {
						name = in.readLine();
						if(name == null)
							return;
						synchronized (usernameList) {
							if(!usernameList.contains(name)) {
								usernameList.add(name);
								obj_out.writeObject("NA:Name accepted");
								break;
							}
						}
						obj_out.writeObject("NNA:Name not accepted");
						continue;
					}
					color = new Color(r.nextInt(256),r.nextInt(256),r.nextInt(256));
					obj_out.writeObject("CS:" + color.getRed() + "," + color.getGreen() + "," + color.getBlue() + ";");
					System.out.println("Player " + name + " reached sleep, not enough players");
					while(plyrs.size() != game.max) {
						Thread.sleep(100L);
					}
					System.out.println("Player " + name + " reached awake, enough players");
					synchronized(game) {
						while(!allConnected) {
							if(plyrs.size() == game.max) {
								allConnected = true;
								break;
							} else
							Thread.sleep(100L);
						}
						
					}
					myOrders = new ArrayList<String>();
					setupDone += 1;
					System.out.println(setupDone);
					System.out.println("setupDone after awakening " + setupDone);
					try {
						barrier.await();
					} catch (InterruptedException | BrokenBarrierException ex) {
						System.out.println("The latch for map updates broke: " + ex.getMessage());
					}
					System.out.println("Player " + name + " entering game loop");
					while(!game.gameOver) {
						while(!lost) {
							synchronized(game) {
								if(mapUpdate)
									mapUpdate = false;
							}
							try {
								timeBarrier.await();
							} catch (BrokenBarrierException e1) {
								System.out.println("Player " + name + " broke the time barrier (un/intentionally)?");
								System.out.println(e1.getMessage());
								continue;
							}
							obj_out.writeObject("ST:" + time);
							long start = System.currentTimeMillis();
							while((System.currentTimeMillis() - start) <= 1000*(60*game.time + 2)) {
								System.out.println(name + " within the time loop");
								try {
									//TODO: Handle connection reset for a client closing the game
									System.out.println(name + " is just before the lock");
										System.out.println(name + " just entered the synchronization lock");
										String tentativeMoves;
										synchronized(readLock) {
											tentativeMoves = in.readLine();
										}
										System.out.println(name + " sent an order");
										if(tentativeMoves.equals("STOP") || tentativeMoves.equals("COMMIT"))
											break;
										System.out.println(name + ": " + tentativeMoves);
										myOrders.add(tentativeMoves);
									Thread.sleep(50L);
								} catch (InterruptedException e) {
									//This is good
									System.out.println("Player " + name + " stopped reading for this turn");
									return;
								}
							}
							setupDone -= 1;
							System.out.println(name + " finished reading");
							synchronized(game) {
								for(String s : myOrders) {
									allOrders.add(parseOrder(s));
								}
								System.out.println(name + ": " + allOrders.size());
								//processOrders(myOrders, this);
								myOrders.clear();
								//turnWorkDone = true;
							}
							System.out.println(name + " at the update barrier");
							try {
								updateBarrier.await();
							} catch (BrokenBarrierException e) {
								System.out.println("Player " + name + " broke the update barrier (un/intentionally)?");
								System.out.println(e.getMessage());
								continue;
							}
						}
					}
					
				} catch (InterruptedException e) {
					System.out.println("Player " + name + " was interrupted!" + e.getMessage());
				} catch (StreamCorruptedException e) { 
					System.out.println("Player " + name + " had its stream broken:" + e.getMessage());
					System.out.println("Localized Message: " + e.getLocalizedMessage());
				} catch (IOException e) {
					System.out.println("Player thread run loop failure: " + e.getMessage());
					e.printStackTrace();
				}
				finally {
					/*try {
						connection.close();
					} catch (IOException e) {
						//Close handle case
					}*/
					//undo the above just to see what happens
				}
			}
		}
	}
}